#include <linux/module.h>
#include <linux/init.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/of_gpio.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/interrupt.h>

/*
	mykeys{
		interrupt-parent = <&gpiof>;
		interrupts = <9 0>,<7 0>, <8 0>; 
		interrupts-name = "key1", "key2", "key3";
		key-gpios = <&gpiof 9 0>, <&gpiof 7 0>, <&gpiof 8 0>;
	};
 */

MODULE_LICENSE("GPL");

typedef struct{
    const char* m_btnName;
    int m_btnIrq;
    struct timer_list m_timer;
    struct gpio_desc* m_pinDesc;
}btn_desc_t;

static btn_desc_t btns[3];
static struct device_node* btnNode;
static dev_t btnDevNum;
static struct cdev* btnCdev;
static struct class* btnClass;
static int cnt = 1;

int btn_fop_open(struct inode *inode, struct file *filp)
{
    //关中断
    unsigned long flags;
    local_irq_save(flags);
	if(--cnt != 0){
		cnt++;
		local_irq_restore(flags);
		return -EBUSY;
	}	
	//开中断
    local_irq_restore(flags);
    return 0;
}
int btn_fop_release(struct inode *inode, struct file *filp)
{
    unsigned long flags;
    local_irq_save(flags);
	cnt++;
	local_irq_restore(flags);
	return 0;
}
ssize_t btn_fop_read (struct file *filp, char __user *buf, size_t len, loff_t *offset)
{
	return len;
}

struct file_operations btnFops = {
    .owner = THIS_MODULE,
    .open = btn_fop_open,
    .release = btn_fop_release,
    .read = btn_fop_read
};

irqreturn_t btn_irq_handle(int irqNum, void* dev)
{
    btn_desc_t* data = (btn_desc_t*)dev;
    mod_timer(&data->m_timer, jiffies + HZ/100);
    return IRQ_HANDLED;
}

void btn_timer_handle(struct timer_list* timer)
{
    int state;
    btn_desc_t* data = container_of(timer,btn_desc_t, m_timer);
    state = gpiod_get_value(data->m_pinDesc);
    printk("%s is %s!\n", data->m_btnName, state?"release": "pressed");
}

static int __init btn_drv_init(void)
{
    int i=0;

    btnNode = of_find_node_by_path("/mykeys");
    for(;i<ARRAY_SIZE(btns);i++)
    {
        btns[i].m_btnIrq = irq_of_parse_and_map(btnNode,i);
        of_property_read_string_index(btnNode, "interrupts-name", i, &btns[i].m_btnName);

        request_irq(btns[i].m_btnIrq, btn_irq_handle, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, 
        btns[i].m_btnName, &btns[i]);

        btns[i].m_pinDesc = gpiod_get_from_of_node(btnNode, "key-gpios", i, GPIOD_IN, btns[i].m_btnName);

        timer_setup(&btns[i].m_timer, btn_timer_handle, 0);
    }

    alloc_chrdev_region(&btnDevNum, 0, 1, "btnDevNum");
    btnCdev = cdev_alloc();
    cdev_init(btnCdev, &btnFops);
    cdev_add(btnCdev,btnDevNum,1);

    btnClass = class_create(THIS_MODULE,"btnClass");
    device_create(btnClass, NULL, btnDevNum ,NULL, "myBtnsDev");

    return 0;
}

static void __exit btn_drv_exit(void)
{
    int i = 0;

    device_destroy(btnClass,btnDevNum);
    class_destroy(btnClass);

    cdev_del(btnCdev);
    unregister_chrdev_region(btnDevNum,1);

    for(;i<ARRAY_SIZE(btns);i++)
    {
        del_timer(&btns[i].m_timer);
        gpiod_put(btns[i].m_pinDesc);
        free_irq(btns[i].m_btnIrq, &btns[i]);
    }
}

module_init(btn_drv_init);
module_exit(btn_drv_exit);